#!/usr/bin/python
#coding=utf-8

import cProfile, pstats
import sys
reload(sys)
sys.setdefaultencoding('utf8')
import os
import logging
import PIL
from PIL import Image,GifImagePlugin,PngImagePlugin
from PIL import ImageFilter,ImageEnhance
from PIL import ImageTk,ImageDraw
from Image import BILINEAR
import math
import time
from cStringIO import StringIO
import cPickle
from itertools import izip
import glob
import Tkinter
import tkMessageBox
from Queue import Queue

from PostGetter import PostGetter

logging.basicConfig(level=logging.DEBUG,
##									format='%(thread)d %(asctime)s %(funcName)s %(message)s',
##									format='%(asctime)s %(name)s %(levelname)s %(funcName)s %(message)s',
format='%(funcName)s %(message)s',
datefmt= '%H:%M:%S')
info,debug=logging.info,logging.debug


class CGetImg(PostGetter):
	u'''识别简单的数字验证码。
	'''
	def __init__(self,cookie_file):
		super(CGetImg,self).__init__(cookie_file)
		self.sc=CSCAlgo(5,12)
		self.cacheSampleData={}


	@staticmethod
	def removeInsularPoint(im,magic=0,bk=255):
		u'''消除孤立点。
		@magic 指定有效像素的值。
		@bk 指定非有效像素(背景)的值。
		返回值：处理后的图片对象。
		'''
		pix=im.load()
		w,h=im.size
		for y in range(h):
			for x in range(w):
				if pix[x,y]==magic:
					nodelist=((x-1, y-1), (x, y-1), (x+1, y-1),\
										(x-1, y),             (x+1, y),\
										(x-1, y+1), (x, y+1), (x+1, y+1))
					if all(( pix[i,j]==bk for i,j in nodelist if -1<i<w and -1<j<h) ):
##						debug('(%d,%d) 是孤立点',x,y)
						pix[x,y]=bk

##		# special for last 10 pixel of last 2 lines
##		for y in range(h-2,h,1):
##			for x in range(w-10,w,1):
		for y in range(h):
			for x in range(w):
				if pix[x,y]==magic:
					rslt=CGetImg.collectPixel(pix,w,h,(x,y),15)
					if len(rslt)<15: # 少于15个像素的块认为是孤立块
						for i,j in rslt:
##							debug('(%d,%d) 是孤立块中的点',i,j)
							pix[i,j]=bk

##		debug('after removeInsularPoint():\n%s',CGetImg.img2string(imgdata))
		return im


	@staticmethod
	def removeSpace(im,magic=0):
		u'''去除图片 @im 四周的白框。 @magic 指定有效像素的值。
		返回值：处理后的图片文件。
		'''
		# 确定上下边界
		edgey=[0,im.size[1]] # 上下
		g=((0,im.size[1],1,0),(im.size[1]-1,0,-1,1))
		for edgeidx,(begin,end,step,offset) in enumerate(g):
			for y in range(begin,end,step):
				row=im.crop((0,y,im.size[0],y+1))
##				bs=list(row.getdata())
##				if bs.count(magic)>0:
				if magic in row.getdata():
					edgey[edgeidx]=y+offset
#					print edgey[idx]
					break
		# 确定左右边界
		edgex=[0,im.size[0]] # 左右
		g=((0,im.size[0],1,0),(im.size[0]-1,0,-1,1))
		for edgeidx,(begin,end,step,offset) in enumerate(g):
			for x in range(begin,end,step):
				col=im.crop((x,0,x+1,im.size[1]))
##				bs=list(col.getdata())
##				if bs.count(magic)>0:
				if magic in col.getdata():
					edgex[edgeidx]=x+offset
#					print edgex[idx]
					break

		im=im.crop((edgex[0],edgey[0],edgex[1],edgey[1]))
##		debug('new size=%s',im.size)
##		debug('after removeSpace():\n%s',CGetImg.img2string(im))
		return im


	@staticmethod
	def img2string(im,magic=0):
		u''' 返回图片 @im 的字符串表示。@magic 指定有效像素的值。
		'''
		toout=''.join(map(lambda x: ' ' if x!=magic else '.',im.getdata()))
		w,h=im.size
		return '\n'.join([toout[i:i+w] for i in range(0,w*h,w)])

	@staticmethod
	def getPixCnt(im, magic=0):
		u''' 返回图片 @im 的总像素数和有效像素数。
		'''
		return im.size[0]*im.size[1], sum( (1 for x in im.getdata() if x==magic) )


	def calSC(self,im,magic=0):
		u'''计算图片 @im 中有效点 @maigc 的对数极坐标直方图。
		返回值：图片的SC值。这是一个dict，key为点的坐标tuple(x,y)，value为(x,y)对应的sc信息为list。
		'''
		w,h=im.size
		pix=im.load()
		log, atan2, hypot, pi, ceil, floor, degrees, radians=\
			 math.log, math.atan2, math.hypot, math.pi, math.ceil, math.floor, math.degrees, math.radians
		r_l=log(hypot(20,20),2)/5 # 每个r的区间长度。假设最大的r为20x20方块的对角线
		points=None
		# 获取所有有效点
		for y in range(h):
			for x in range(w):
				# 找到第一个有效点
				if pix[x,y]==magic:
					points=self.collectPixel(pix,w,h,(x,y)) # 返回和此点连通的有效点(包含此点)
					break
			if points:break

		# calc scale
		return self.sc.setRadiusMatrix(points)


	@staticmethod
	def collectPixel(imga,w,h,startpoint,nr_limit=0,eight=True,excludelist=None):
		u'''将点@startpoint所在字符的所有点(包含 @startpoint 本身)放入列表返回。
		@imga 图像。
		@w 图像宽度。
		@h 图像高度。
		@startpoint 初始点。
		@nr_limit 限制找到的像素数量，达到则提前返回。默认为0，表示无限制。
		@eight 是否是八连通，默认是True，如果设为False则为四连通。
		@excludelist 排除列表。包含在此列表中的点将不会被搜索而是认为是边缘。
		返回值：list，元素为 tuple(x,y)。
		'''
		pixel_not_search=[]
		pixel_found=[]

		magic=imga[startpoint[0], startpoint[1]]
		pixel_found.append(startpoint)
		pixel_not_search.append(startpoint)

		def find_pixel(imga,found,x,y):
			u'''返回新找到的与点(x,y)直接相邻的属于字符的点'''
			if eight:
				nodelist=[
					(x-1, y-1), (x, y-1), (x+1, y-1),\
					(x-1, y),             (x+1, y),\
					(x-1, y+1), (x, y+1), (x+1, y+1)]
			else:
				nodelist=[
					(x, y-1),          \
					(x-1, y),             (x+1, y),\
					(x, y+1)             ]

			for i,j in nodelist:
				if -1<i<w and -1<j<h:
					if imga[i,j]==magic:
						if excludelist and  (i,j) in excludelist:
##								debug('exclude (%d,%d) !',i,j)
							continue
						if (i,j) not in found:
							yield  (i,j)

		while pixel_not_search:
			try:
				p=pixel_not_search.pop()
				for x,y in find_pixel(imga,pixel_found,*p):
					pixel_found.append((x,y))
					if nr_limit and len(pixel_found)==nr_limit: # reach nr_limit
						del pixel_not_search[:] # to break from while loop
						break
					if (x,y) not in pixel_not_search:
						pixel_not_search.append((x,y))

			except IndexError:
				break

##		# 清掉分割过的字符
##		for x,y in pixel_found:
##			imga[x][y]=' '
		return pixel_found


	def calCost(self, sampleFilePath, sc2cmp, threshold=0.1):
		u'''计算直方图之间的cost值。
		@sampleFilePath 样本文件目录。
		@sc2cmp 未知图片直方图集合 坐标为key，对应的直方图list为value。
		返回值：list，每个元素为tuple(样本文件索引（与文件名相同），对应的匹配次数)。
		'''
		debug=self.logger.debug
		matchlist=[]
		sampleData=None
##		cc=self.sc.calcTwoHistogramsCost

		# pre calc, 使 calcTwoHistogramsCost 提高速度
		try:
			sampleData=self.cacheSampleData[sampleFilePath]
##			debug('cache hit.')
		except KeyError:
			sampleData=[]
			for i in range(1,10): # 读取1-9 共9个数字的直方图文件
				sampleData.append(cPickle.load(open(os.path.join(sampleFilePath,'%d.dat'%i),'rb')))
			for sam in sampleData:
				for k in sam.keys():
					tmpsum=sum(sam[k])
					sam[k]=[ float(m)/tmpsum for m in sam[k] ]
			self.cacheSampleData[sampleFilePath]=sampleData

		for k in sc2cmp.keys():
			tmpsum=sum(sc2cmp[k])
			sc2cmp[k]=[ float(m)/tmpsum for m in sc2cmp[k] ]

		for i,sample in enumerate(sampleData): # 对每个sample文件
			match_cnt=0 # 未知图片的某个点匹配当前sample文件的点直方图的次数
##			cost_sum=0
			for v1 in sc2cmp.itervalues(): # 对未知图片每一个点的直方图
				mincost=100000
				for v0 in sample.itervalues(): # 对当前sample文件中每个点的直方图
##					assert len(v0)==60 and (len(v0)==len(v1))
##					cost=cc(v0,v1)
					# 不调用 calcTwoHistogramsCost
					cost=sum( [ ((va1 - va2)**2)/(va1 + va2) for va1,va2 in izip(v0, v1) if va1+va2!=0.0  ] ) /2
					if cost<mincost:
						mincost=cost
				if mincost < threshold: # 小于阀值，则认为是一个匹配点
					match_cnt+=1
##			matchlist.append((i+1,cost_sum/match_cnt if match_cnt else cost_sum,match_cnt)) # 记录sample文件索引(名称)和对应的匹配次数
			matchlist.append((i+1,match_cnt)) # 记录sample文件索引(名称)和对应的匹配次数

##		assert len(matchlist)==9
		matchlist.sort(key=lambda x:x[1],reverse=True)

##		debug('%s',matchlist)
		return matchlist


	def recognizeImg(self,im,sampledir,showdbg=True):
		u'''识别图片 @im 上的数字，样本文件所在目录为 @sampledir 。
		返回值：list，每个元素对应图片上每个字符，元素为list, 内部为按照匹配次数降序排列的tuple(样本文件索引，匹配次数)。
		'''
		info,debug=self.logger.info,self.logger.debug
		t=self.preSmartSplitImg(im)
		if showdbg:
			info('after preSmarkSplitImg():\n%s',self.img2string(t))

		t=self.smartSplitImg(t)
		t=[self.removeInsularPoint(x) for x in t ]
		t=[ self.keepEdge(x) for x in t]
		t=[ self.removeSpace(x) for x in t]

		if showdbg:
			info('calSC ...\n')
		sclist=[]
		for im in t:
			st=self.calSC(im)
			sclist.append((im,st))

		if showdbg:
			info('calCost ...\n')
		mlist=[]
		for idx,(im,sc) in enumerate(sclist):
			if showdbg:
				info('\t %d)\n%s',idx,self.img2string(im))
			mlist.append(self.calCost(sampledir,sc))

		return mlist #


	def doWork(self,im=None,test=False):
		info,debug=self.logger.info,self.logger.debug
		if im:
			l=self.recognizeImg(im, '/home/kevin/data_bk/python/postgetter-app/scimgdata/normal-shit/')
			return ''.join((str(x[0][0]) for x in l))

		if test:
			im=GifImagePlugin.GifImageFile('/home/kevin/tmp.gif')
			im.seek(0)
		else:
			r,_,_=self._getResponse('http://www.sbanzu.com/sbanzu/GetCode.asp'.encode(self.dft_img_encoding))
			if not r:
				info('cant\'t get code image!')
				return False
			else:
				im=GifImagePlugin.GifImageFile(StringIO(r))
				open('/home/kevin/tmp.gif','wb').write(r)
				im.seek(0)
				l=self.recognizeImg(im, '/home/kevin/proj/hg/postgetter-app/scimgdata/bk/')
				return


	@staticmethod
	def keepEdge(im,magic=0,bk=255):
		u''' 保留图片 @im 中字符的边缘，非边缘则设为背景色。
		@magic 有效字符的颜色。
		@bk 背景颜色。
		返回值：处理后的图片对象。
		'''
		w,h=im.size
		pix=im.load()
		edge=set()
		for y in range(h):
			for x in range(w):
				if x-1>-1 and pix[x,y]!=pix[x-1,y]:
					if pix[x,y]==magic:
						edge.add((x,y))
					else:
						edge.add((x-1,y))
				if y-1>-1 and pix[x,y]!=pix[x,y-1]:
					if pix[x,y]==magic:
						edge.add((x,y))
					else:
						edge.add((x,y-1))
				if pix[x,y]==magic and (x,y) not in edge:
					pix[x,y]=bk

##		debug('after keepEdge():\n%s',CGetImg.img2string(im))
		return im


	def preSmartSplitImg(self,im):
		u'''智能切分图片前的预处理。
		@im 图片对象。
		返回值：预处理后的图片对象。
		'''
		im=im.convert('1')
		im=self.removeInsularPoint(im)
		return im


	@staticmethod
	def smartSplitImg(im,magic=0,bk=255):
		u'''更智能的分割图片 @im 中的字符，返回包含字符图片的列表。 @magic 指定有效像素的值。
		返回值：分割后的图片对象列表。
		'''
		result=[]
		w,h=im.size
		pix=im.load()
		# 80个像素有四个数字，则每个大概是20个像素宽(包含空白)。因此以每20个像素为一个大致的纵向分割点，并在此左右5个
		# 像素的宽度内查找无粘连纵向分割点的x坐标。	如果没有无粘连切割点则对应x值填None。
		parts=[0,]# 记录3个纵向切割点
		for idx,x in enumerate(range(20,w,20)):
			mincnt,bestidx=h,None
			for i in range(x-5,x+5,1):
				col=im.crop((i,0,i+1,h))
				cnt=list(col.getdata()).count(magic)
				if mincnt>=cnt:
					mincnt=cnt
					bestidx=i
				if cnt==0: # 字符不沾连
##					debug('cnt==0 at %d',i)
##					bestidx=i
					break
##				else:
##					debug('cnt==%d at %d',cnt,i)

			if mincnt==0: # 无粘连且可以被一条纵向直线切分
				result.append(im.crop((parts[idx],0,bestidx,h)))
				parts.append(bestidx)
			else: # 存在粘连或者不能被一条纵向直线切分
				debug('%s\nchars joined or can\'t be cut by straight line at chars %d','~*'*20,idx+1)
				tmpim=im.crop((x-5-1,0,x+5+1,h)) # 多加多减一个1是因为下面封锁两边时各多占用了一个像素
				tmpw,tmph=tmpim.size
				tmppix=tmpim.load()
				# 封锁两边
				yy=0
				while yy<tmph and tmppix[0,yy]==bk:
					tmppix[0,yy]=magic
					yy+=1
				yy=0
				while yy<tmph and tmppix[tmpw-1,yy]==bk:
					tmppix[tmpw-1,yy]=magic
					yy+=1
				debug('\n%s',CGetImg.img2string(tmpim))


				pixlist=None
				for yy in range(tmph):
					for xx in range(tmpw):
						if tmppix[xx,yy]==bk: # 找到第一个背景点
							pixlist=CGetImg.collectPixel(tmppix,tmpw,tmph,(xx,yy),eight=False) # 收集所有相连的背景点
							break
					if pixlist:
						break
				maxy=max(pixlist,key=lambda ttt:ttt[1])[1] # 背景点中最大的 y 值
				pixs_with_maxy=[(xx,yy) for xx,yy in pixlist if yy==maxy] # 最大 y 值对应的点list
				pixs_with_maxy.sort(key=lambda ttt:ttt[0]) # 按照x值排序
				debug('max y=%d, pix cnt %d',maxy,len(pixs_with_maxy))

				if maxy<=h/2: # 上半部分发生粘连
					min_mid=100000
					min_mid_x=None
					if len(pixs_with_maxy)==1: # 就一个点，直接取其x为切分点
						min_mid_x=pixs_with_maxy[0][0]
					else:
						for xx,_ in pixs_with_maxy: # 找y相同的点中最接近中心点的点，其 x 为切分点。
							if abs(xx-5-1)<min_mid:
								min_mid=abs(xx-5-1)
								min_mid_x=xx
					min_mid_x+=x-5-1 # 坐标变换回原图片的

					debug('maxy<=h/2 %d<=%d, best x=%d, crop (%d,0,%d,%d)',maxy,h/2,min_mid_x,parts[idx],min_mid_x,h)
					cropim=im.crop((parts[idx],0,min_mid_x,h))
					cropim.load()
					result.append(cropim)
					parts.append(min_mid_x)

					# 将切线写入原图片，下面收集像素时切线位置将被认为是断开的，防止收集到右侧字符的像素
					excludes=[]
					for xx in range(min_mid_x,min_mid_x+2,1):
						for yy in range(maxy,h,1):
##							debug('add excludes (%d, %d)...',xx,yy)
							excludes.append((xx,yy))

					# 收集字符像素
					for xx in range(x-5,x+5,1):
						for yy in range(h):
							if pix[xx,yy]==magic:
								pixlist=CGetImg.collectPixel(pix,w,h,(xx,yy),excludelist=excludes)
								break
						if pixlist:
							break

					# 清除原图上的字符
					for xx,yy in pixlist:
						pix[xx,yy]=bk


				elif maxy<h-1: # 下半部分发生粘连
					min_level_x=None
					min_level=None
					if len(pixs_with_maxy)==1: # 就一个点，直接取其x为切分点
						min_level_x=pixs_with_maxy[0][0]
					else: # 找从此点切下遇到的层数最少的x，且x要接近中心点
						x_level=[]
						for xx,_ in pixs_with_maxy:
							x_level.append((xx,CGetImg.getLevel(tmpim,(xx,maxy))))
						min_level_x,min_level=min(x_level,key=lambda ttt: abs(ttt[0]-5-1))
					min_level_x+=x-5-1 # 坐标变换回原图片的

					# 将切线写入原图片，下面收集像素时切线位置将被认为是断开的，防止收集到右侧字符的像素
					excludes=[]
					for xx in range(min_level_x,min_level_x+2,1):
						for yy in range(maxy,h,1):
##							debug('add excludes (%d, %d)...',xx,yy)
							excludes.append((xx,yy))

					# 收集字符像素
					for xx in range(x-5,x+5,1):
						for yy in range(h):
							if pix[xx,yy]==magic:
								pixlist=CGetImg.collectPixel(pix,w,h,(xx,yy),excludelist=excludes)
								break
						if pixlist:
							break
					debug('got %d pixels',len(pixlist))
					char_min_x=min(pixlist,key=lambda ttt:ttt[0])[0]
					char_w=max(pixlist,key=lambda ttt:ttt[0])[0]-char_min_x+1
					char_min_y=min(pixlist,key=lambda ttt:ttt[1])[1]
					char_h=max(pixlist,key=lambda ttt:ttt[1])[1]-char_min_y+1

					char_im=Image.new(im.mode,(char_w,char_h),bk) # 构造只包含左侧字符的新图片对象
					char_pix=char_im.load()
					for xx,yy in pixlist:
						char_pix[xx-char_min_x,yy-char_min_y]=magic
						pix[xx,yy]=bk # 清除掉原图上的相应字符

					debug('h/2<maxy<h %d<%d<%d, best=%d, level=%s',h/2,maxy,h,min_level_x,str(min_level) if min_level else 'not calc')
					result.append(char_im)
					parts.append(min_level_x) # 取需要切分层数最少的x

				else: # 不粘连
					# 收集字符像素
					pixlist=None
					for xx in range(x-5,x+5,1):
						for yy in range(h):
							if pix[xx,yy]==magic:
								pixlist=CGetImg.collectPixel(pix,w,h,(xx,yy))
								break
						if pixlist:
							break
					char_min_x=min(pixlist,key=lambda ttt:ttt[0])[0]
					char_w=max(pixlist,key=lambda ttt:ttt[0])[0]-char_min_x+1
					char_min_y=min(pixlist,key=lambda ttt:ttt[1])[1]
					char_h=max(pixlist,key=lambda ttt:ttt[1])[1]-char_min_y+1

					char_im=Image.new(im.mode,(char_w,char_h),bk) # 构造只包含左侧字符的新图片对象
					char_pix=char_im.load()
					for xx,yy in pixlist:
						char_pix[xx-char_min_x,yy-char_min_y]=magic
						pix[xx,yy]=bk # 清除掉原图上的相应字符

					debug('chars not joined! maxy==h %d==%d,best=%d',maxy,h-1,x-5)
					result.append(char_im)
					parts.append(x-5) # 取最左侧的x，反正原图上已经没有左侧的字符了，不怕右测字符切到左侧字符。


		result.append(im.crop((parts[-1],0,w,h)))

		assert len(result)==4
		return result


	@staticmethod
	def getLevel(im,startpoint,magic=0):
		pix=im.load()
		x,y=startpoint
		cnt=0
		for y in range(y+1,im.size[1]):
			if  pix[x,y]==magic and y-1>-1 and pix[x,y-1]!=pix[x,y]:
				cnt+=1

		return cnt


class CSCAlgo(object):
	TotalGrid=None

	def __init__(self, NoBins=5, NoAngles=12):
		super(CSCAlgo,self).__init__()
		self.NoBins=NoBins
		self.NoAngles=NoAngles
		CSCAlgo.TotalGrid=self.NoBins*self.NoAngles
		self.logSpace= None
		self.setLogSpace(math.log10(0.125), math.log10(2), self.NoBins)


	def setLogSpace(self, i_LowBoundery, i_HighBoundery, i_NumOfSlices):
		pi, log10 = math.pi, math.log10

		if i_HighBoundery == pi:
			i_HighBoundery = log10(pi)

		self.logSpace= [ 0.0 for _ in range(i_NumOfSlices)]
		distance = i_HighBoundery - i_LowBoundery
		numOfSlices = i_NumOfSlices - 1

		for i in range(numOfSlices):
			self.logSpace[i] = pow(10, i_LowBoundery + i * distance / numOfSlices)
		self.logSpace[numOfSlices] = pow(10, i_HighBoundery)


	def setRadiusMatrix(self, i_Points):
		log10, hypot, atan2, floor, ceil, pi2 = math.log10, math.hypot, math.atan2, math.floor, math.ceil, 2*math.pi
		sumDist = 0
		euclideDist = 0
		numOfSamples = len(i_Points)
		radiusMatrix = [ 0 for _ in range(numOfSamples**2)]

		def getDistance(p0,p1):
			dx,dy=p1[0] - p0[0], -(p1[1]-p0[1]) # PIL的y轴向下为正(左上角为原点)，这与迪卡尔坐标系相反，因此y需要加负号
			r=hypot(dx, dy)
			return r

		def getAngle(p0,p1):
			dx,dy=p1[0] - p0[0], -(p1[1]-p0[1]) # PIL的y轴向下为正(左上角为原点)，这与迪卡尔坐标系相反，因此y需要加负号
			t= atan2(dy, dx)
			if t<0:
				t+=pi2
##			assert t>=0
			return abs(t)
	##		return t

		# set distance
		for i in range(numOfSamples): # symemtric metrix
			for j in range(i,numOfSamples,1):
				r= getDistance(i_Points[i], i_Points[j])
				radiusMatrix[i*numOfSamples+ j] = [r, getAngle(i_Points[i],i_Points[j])]
				radiusMatrix[j*numOfSamples+ i] = [r, getAngle(i_Points[j],i_Points[i])]

				sumDist += (r * 2) # Because we're symmetric - running only on a half

	##	# debug
	##	toout=map(lambda x:'(%s, %s)'%( ('%.4f'%round(x[0],4)).zfill(6), ('%.4f'%round(x[1],4)).zfill(6) ),radiusMatrix)
	##	debug('got:\n%s','\n'.join( ( '  '.join((x for x in toout[i:i+numOfSamples])) for i in range(0,numOfSamples**2,numOfSamples) ) ) )

		# 均一化
		o_Avg = sumDist / (numOfSamples**2)
		if o_Avg:
			radiusMatrix = [ [r/o_Avg,t] for r,t in radiusMatrix]

		upperLogSpaceValue = self.logSpace[self.NoBins - 1]

	##	# debug
	##	toout=map(lambda x:'(%s, %s)'%( ('%.4f'%round(x[0],4)).zfill(6), ('%.4f'%round(x[1],4)).zfill(6) ),radiusMatrix)
	##	debug('\n\ngot:\n%s','\n'.join( ( '  '.join((x for x in toout[i:i+numOfSamples])) for i in range(0,numOfSamples**2,numOfSamples) ) ) )

		for i in range(numOfSamples): # row
			for j in range(numOfSamples): # col
				idx=i*numOfSamples+ j # same as radiusMatrix[i,j]
				r,t = radiusMatrix[idx]
				radiusMatrix[idx] = [-1, t]

				if r > upperLogSpaceValue:
					continue

				for k in range(self.NoBins): # find the right radius
					if r < self.logSpace[k]:
						radiusMatrix[idx] = [ self.NoBins-k, t]
						break

	##	radiusMatrix= [ [r, 1+floor(t/(pi2/6))] for r,t in radiusMatrix ]
		# simple Quantization
		radiusMatrix= [ [r, 1+int(floor(t/(pi2/self.NoAngles)))] for r,t in radiusMatrix ]

##		# debug
##		for r,t in radiusMatrix:
##			assert 0 < t < 13

	##	# debug
	##	toout=map(lambda x:'(%s, %s)'%('%d'%x[0], '%d'%x[1]),radiusMatrix)
	##	debug('\n\ngot:\n%s','\n'.join( ( '  '.join((x for x in toout[i:i+numOfSamples])) for i in range(0,numOfSamples**2,numOfSamples) ) ) )

		# 将点对点的位置矩阵转换为 每个点：其他点在对应的区间内的落点数
		# like Histogram.setBinsPointReference(...)
		sc={}
		for i in range(numOfSamples): # row
			p=i_Points[i]
			sc1point=[ 0 for _ in range(self.NoBins*self.NoAngles) ]
			for j in range(numOfSamples): # col
				if i==j:
					continue
				r,t=radiusMatrix[i*numOfSamples+j]
				sc1point[(r-1)*self.NoAngles+t-1]+=1

	##		# debug
	##		toout = map(lambda x:'%d'%x,sc1point)
	##		debug('\n\ngot:\n%s', '\n'.join( ( '  '.join((x for x in toout[i:i+self.NoAngles])) for i in range(0, self.NoBins*self.NoAngles, self.NoAngles) ) ) )
	##		raw_input('Press ENTER to continue ...')

			sc[p] = sc1point

##		assert len(sc) == numOfSamples
	##	debug('got sc')
		return sc


	@staticmethod
	def calcTwoHistogramsCost(i_Histogram1, i_Histogram2):
		return sum( [ ((va1 - va2)**2)/(va1 + va2) for va1,va2 in izip(i_Histogram1, i_Histogram2) if va1+va2!=0.0  ] ) /2

##		sum_all , histogram1Sum, histogram2Sum = 0, sum(i_Histogram1), sum(i_Histogram2)
##		for i in range(CSCAlgo.TotalGrid):
##			val1, val2 = float(i_Histogram1[i])/histogram1Sum, float(i_Histogram2[i])/histogram2Sum
##			if val1 + val2:
##				sum_all += ( (val1 - val2)**2 ) / (val1 + val2)
##		return sum_all / 2

	@staticmethod
	def calCostMatrix(sc1, sc2):
		''' like ShapeContextMatching.calculateCostMatrix(...)
		'''
		n1, n2=len(sc1), len(sc2)
		orderedlist1, orderedlist2 = sc1.keys(), sc2.keys()

		costMatrix=[ -1 for _ in range(n1*n2) ]
		for i in range(n1):
			sc1point1=sc1[ orderedlist1[i] ]
			for j in range(n2):
				sc1point2=sc2[ orderedlist2[j] ]
				idx=i*n1+j
				costMatrix[idx] = CSCAlgo.calcTwoHistogramsCost(sc1point1,sc1point2)

		for x in costMatrix:
			assert x!=-1 and 0<x<1
		return costMatrix


	@staticmethod
	def determineBestDistance(m_costMatrix, rowcnt, colcnt):
		''' copy from http://code.google.com/p/shape-matching/source/browse/ShapeContext/ShapeContextMatching.cs
/// <summary>
        /// Better way (over than Hungarian) to make pairing when there is a possibility of non-perfect pairing.
        /// Will find best pairing if the targets number is more than source. In such way the chances are for better match.
        /// </summary>
        /// <param name="m_costMatrix"></param>
        /// <param name="o_MatchingData"></param>
        /// <returns></returns>
        private int[] determineBestDistance(DoubleMatrix m_costMatrix,out Dictionary<int, Pair<int, double>> o_MatchingData)
        {
            int[] retSuiteIndexes = new int[m_costMatrix.RowsCount];

            o_MatchingData = new Dictionary<int, Pair<int, double>>();
            Queue<int> waitingToMatch = new Queue<int>();

            //Initializing queue
            for (int row = 0; row < m_costMatrix.RowsCount; ++row)
            {
                waitingToMatch.Enqueue(row);
            }

            while (waitingToMatch.Count != 0)
            {
                int currSourceIndex = waitingToMatch.Dequeue();

                int lowIndex = -1;
                double lowestValue = double.MaxValue;


                for (int col = 0; col < m_costMatrix.ColumnsCount; ++col)
                {
                    if (lowestValue > m_costMatrix[currSourceIndex, col])
                    {
                        bool isBetterMatch = false;

                        if ((o_MatchingData.ContainsKey(col)) &&
                            (o_MatchingData[col].Element2 > m_costMatrix[currSourceIndex, col]))
                        {

                            waitingToMatch.Enqueue(o_MatchingData[col].Element1);
                            o_MatchingData.Remove(col);
                            isBetterMatch = true;

                        }

                        if ((isBetterMatch) || (!o_MatchingData.ContainsKey(col)))
                        {
                            o_MatchingData.Remove(lowIndex);
                            lowestValue = m_costMatrix[currSourceIndex, col];
                            lowIndex = col;

                            Pair<int, double> matchData;
                            matchData.Element1 = currSourceIndex;
                            matchData.Element2 = lowestValue;
                            o_MatchingData.Add(lowIndex, matchData);
                        }

                    }
                }

                retSuiteIndexes[currSourceIndex] = lowIndex;
            }

            return retSuiteIndexes;
        }


		'''
		retSuiteIndexes = [ 0 for _ in range(rowcnt) ]

		o_MatchingData = {} # key=int(col index), value=[int(row index), double(mini cost)]
		waitingToMatch = Queue()

		# Initializing queue
		for row in range(rowcnt):
			waitingToMatch.put(row)

		while not waitingToMatch.empty():
			currSourceIndex = waitingToMatch.get()

			lowIndex, lowestValue = -1, 100000.0

			for col in range(colcnt):
				if lowestValue > m_costMatrix[currSourceIndex*colcnt+ col]:
					isBetterMatch = False

					if ( col in o_MatchingData ) and ( o_MatchingData[col][1] > m_costMatrix[currSourceIndex*colcnt+ col] ):
						waitingToMatch.put(o_MatchingData[col][0])
						del o_MatchingData[col]
						isBetterMatch = True

					if isBetterMatch or ( col not in o_MatchingData):
						if lowIndex != -1:
							del o_MatchingData[lowIndex]
						lowestValue = m_costMatrix[currSourceIndex*colcnt+ col]
						lowIndex = col

						o_MatchingData[lowIndex] = [currSourceIndex, lowestValue]


			retSuiteIndexes[currSourceIndex] = lowIndex

		return retSuiteIndexes, o_MatchingData


class CGetImg_test(CGetImg):

	# 索引表，用于细化图像
	array = [0,0,1,1,0,0,1,1,1,1,0,1,1,1,0,1,\
				   1,1,0,0,1,1,1,1,0,0,0,0,0,0,0,1,\
				   0,0,1,1,0,0,1,1,1,1,0,1,1,1,0,1,\
				   1,1,0,0,1,1,1,1,0,0,0,0,0,0,0,1,\
				   1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,\
				   0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,\
				   1,1,0,0,1,1,0,0,1,1,0,1,1,1,0,1,\
				   0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,\
				   0,0,1,1,0,0,1,1,1,1,0,1,1,1,0,1,\
				   1,1,0,0,1,1,1,1,0,0,0,0,0,0,0,1,\
				   0,0,1,1,0,0,1,1,1,1,0,1,1,1,0,1,\
				   1,1,0,0,1,1,1,1,0,0,0,0,0,0,0,0,\
				   1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,\
				   1,1,0,0,1,1,1,1,0,0,0,0,0,0,0,0,\
				   1,1,0,0,1,1,0,0,1,1,0,1,1,1,0,0,\
				   1,1,0,0,1,1,1,0,1,1,0,0,1,0,0,0]

	def __init__(self,cookie_file):
		super(CGetImg_test,self).__init__(cookie_file)


	def testSplitImg(self,imgdir,sampledir,badsampledir):
		u'''将目录 @imgdir 下以其内容命名的gif文件切分为单个字符的文件。
		如果切分正确，则将单个字符的文件放入目录 @sampledir 中以相应字符命名的目录下。
		如果切分错误，则只将原始文件保存到目录 @badsampledir 中。
		注意：目录 @imgdir 下的文件在处理后将被删除。
		返回值：无。
		'''
		info,debug=self.logger.info,self.logger.debug
		total,right,sec=0,0,0
		for fname in glob.iglob(os.path.join(imgdir,'*.gif')):
			f=os.path.basename(fname)
			rawim=GifImagePlugin.GifImageFile(fname)
			rawim.seek(0)

			t=self.binarizationImg(rawim)
##			info('after binarizationImg():\n%s',self.img2string(t))

			t=self.removeInsularPoint(t)
##			info('after removeInsularPoint():\n%s',self.img2string(t))

			info('after keepEdge():\n%s',self.img2string(t))
			t=self.keepEdge(t)

			info('after splitImg():\n')
			t=self.splitImg(t)

			# 根据切割的宽度反向获取未二值化的单个字符的图片
			rawchars=[]
			x=0
			for item in t:
				rawchars.append(rawim.crop((x,0,x+item.size[0],rawim.size[1])))
				x+=item.size[0]

			for idx, im in enumerate(t):
##				st=self.calSC(im)
##				sclist.append((im,st))
				info('\t %d)\n%s',idx,self.img2string(im))
			s=raw_input('ENTER to skip, other keys to save to %s:'%badsampledir)
			if s!='':
				fpath=os.path.join(badsampledir,f)
				rawim.save(fpath,'gif')
##				rawchars[idx].save(fpath,'gif')
				info('%s saved.',fpath)
			else:
				for idx, im in enumerate(rawchars):
					fpath=os.path.join(imgdir,f[idx])
					fpath=os.path.join(fpath,'%s%f.png'%(f[idx],time.time()))
					im.save(fpath)
					info('%s saved.',fpath)
			os.remove(fname)


	def trySplitImg(self,badsampledir):
		u'''测试对目录 @badsampledir 下图片文件的字符分割，显示在GUI中。
		返回值：无。
		'''
		info,debug=self.logger.info,self.logger.debug
		scale=4 # zoom in
		flist=glob.glob(os.path.join(badsampledir,'*.gif'))
		def BtnNext(evt=None):
			def getOneImg():
				try:
					fname=flist.pop(0)
				except IndexError:
##					debug('no more')
					return None
				else:
					f=os.path.basename(fname)
					rawim=GifImagePlugin.GifImageFile(fname)
					rawim.seek(0)

##					t=self.binarizationImg(rawim)
					t=self.preSmartSplitImg(rawim)
					binim=t.resize((t.size[0]*scale,t.size[1]*scale),BILINEAR)


					t=self.smartSplitImg(t)
					splitim=[ x.resize((x.size[0]*scale,x.size[1]*scale),BILINEAR) for x in t]

					t=[self.removeInsularPoint(x) for x in t ]
					insularim=[ x.resize((x.size[0]*scale,x.size[1]*scale),BILINEAR) for x in t]

##					xihua=[ self.getEndPoint(x) for x in t]
##					xihuaim=[ x.resize((x.size[0]*scale,x.size[1]*scale),BILINEAR) for x in xihua]

					t=[ self.keepEdge(x) for x in t]
					edgeim=[ x.resize((x.size[0]*scale,x.size[1]*scale),BILINEAR) for x in t]


					# 根据切割的宽度反向获取未二值化的单个字符的图片
					rawchars=[]
					x=0
					for item in t:
						tmp=rawim.crop((x,0,x+item.size[0],rawim.size[1]))
						tmp=tmp.resize((tmp.size[0]*scale,tmp.size[1]*scale),BILINEAR)
						rawchars.append(tmp)
						x+=item.size[0]

					# 在原图上画线
					draw=ImageDraw.Draw(rawim)
					x=0
					# 画上实际切分线
					for item in t[:-1]:
						x+=item.size[0]
						draw.line((x,0)+(x,2),fill=64,width=1)
					# 画上理论切分区域
					for x in range(20,rawim.size[0],20):
						draw.line((x,0)+(x,1),fill=128,width=0)
						for i in range(x-5,x+5,1):
							if i!=x:
##								draw.line((i,0)+(i,(5-abs(i-x))),fill=168,width=0)
								draw.line((i,0)+(i,0),fill=168,width=0)
					largeim=rawim.resize((rawim.size[0]*scale,rawim.size[1]*scale),BILINEAR)

					t=[ self.removeSpace(x) for x in t]
					removespaceim=[ x.resize((x.size[0]*scale,x.size[1]*scale),BILINEAR) for x in t]


					return {'largeim': ImageTk.PhotoImage(largeim), 'binim':ImageTk.PhotoImage(binim),\
									'splitim': [ImageTk.PhotoImage(x) for x in splitim],
									'insularim': [ImageTk.PhotoImage(x) for x in  insularim],
									'edgeim': [ImageTk.PhotoImage(x) for x in edgeim],
									'removespaceim': [ImageTk.PhotoImage(x) for x in removespaceim],
									'rawchars': [ImageTk.PhotoImage(x) for x in rawchars]}
##					       'xihuaim': [ImageTk.PhotoImage(x) for x in xihuaim]}

			img=getOneImg()
			if not img:
				tkMessageBox.showwarning('no more','no more image to show!')
				return
			canvas.delete('whole')
			x,y=2,2

			canvas.create_image(x,y,image=img['largeim'],anchor = Tkinter.NW,tags=('whole')) # 根据PhotoImage显示图像到画布上
			y+=img['largeim'].height()+5
			canvas.create_image(x,y,image=img['binim'],anchor = Tkinter.NW,tags=('whole'))


##			y+=max((x.height() for x in img['insularim']))+5
			y+=img['binim'].height()+5
			x=2
			for item in img['splitim']:
				canvas.create_image(x,y,image=item,anchor = Tkinter.NW,tags=('whole'))
				x+=item.width()+6

			y+=max((x.height() for x in img['splitim']))+5
##			y+=img['binim'].height()+5
			x=2
			for item in img['insularim']:
				canvas.create_image(x,y,image=item,anchor = Tkinter.NW,tags=('whole'))
				x+=item.width()+6

			y+=max((x.height() for x in img['insularim']))+5
			x=2
			for item in img['edgeim']:
				canvas.create_image(x,y,image=item,anchor = Tkinter.NW,tags=('whole'))
				x+=item.width()+6


			y+=max((x.height() for x in img['edgeim']))+5
			x=2
			for item in img['removespaceim']:
				canvas.create_image(x,y,image=item,anchor = Tkinter.NW,tags=('whole'))
				x+=item.width()+6

			y+=max((x.height() for x in img['removespaceim']))+5
			x=2
			for item in img['rawchars']:
				canvas.create_image(x,y,image=item,anchor = Tkinter.NW,tags=('whole'))
				x+=item.width()+6

##			x=2
##			y+=max((x.height() for x in img['rawchars']))+5
##			for item in img['xihuaim']:
##				canvas.create_image(x,y,image=item,anchor = Tkinter.NW,tags=('whole'))
##				x+=item.width()+6
##			canvas.create_image(x,y,image=img['xihuaim'],anchor = Tkinter.NW,tags=('whole'))


			# this code doest nothing and always get TclError: unknown option "-outline"
			# but the point is: canvas.update() will not work without this code! it's strange but true.
			canvas.itemconfigure('whole',outline='white')
			canvas.update()

		root=Tkinter.Tk() # 主窗口
		root.geometry('640x768') # 设置主窗口大小
		f=Tkinter.Frame(root) # 纯容器

		canvas=Tkinter.Canvas(f,width=600,height=700) # 画布
		pic=Tkinter.PhotoImage() # 图像
##		canvas.create_image(0,0,image=pic,anchor = Tkinter.NW,tags=('whole')) # 根据PhotoImage显示图像到画布上
		canvas.config(bg='darkgray',bd=0) # 设置画布背景色和边框宽度
		canvas.pack(side=Tkinter.TOP,expand=False)#expand = True, side=Tkinter.LEFT,fill = Tkinter.BOTH)
		f.pack()

		nb=Tkinter.Button(root,text='next',command=BtnNext)
		nb.bind('<Return>',BtnNext)
		qb=Tkinter.Button(root,text='QUIT',bg='red',fg='white',command=root.quit) # 退出按钮
		qb.pack(fill=Tkinter.X,side=Tkinter.BOTTOM)#,expand=True)
		nb.pack(fill=Tkinter.X,side=Tkinter.BOTTOM)#,expand=True)
		nb.focus_force() # 强制获得输入焦点
		root.after(10,BtnNext)
		root.title('show')
		root.mainloop()
		root.destroy()


	@staticmethod
	def showThinnerTable():
		u'''显示细化算法用到的索引表。细化算法用的是查表法，根据有效点周围8个相邻格子的情况决定是否保留此有效点。
		判断基本依据：
		1，内部点不能删除
		2，孤立点不能删除
		3，直线端点不能删除
		4，如果P是边界点，去掉P后，如果连通分量不增加，则P可删除
		表格共256项，其索引代表有效点周围相邻8个格的分布情况，其数值代表是否删除有效点，1 为删除，0 为不删除。

		索引的二进制(8位)与8个格子存在对应关系：低位对应低索引格子高位对应高索引格子。
		也就是说二进制的最低位代表左上角的格子，最高位代表右下角的格子。正中间的格子在二进制中
		没有对应位。0表示此格为有效点，1代表非有效点。
		[1] [2] [4]        [0][1][2]
		[8] [X] [16]   ==> [3][ ][4]
		[32][64][128]      [5][6][7]

		例如表格第1项的索引为0，值为0，表示有效点周围也均为有效点，因此不删除；
		再比如表格第38项的索引为37(二进制为00100101(1+4+32),反序为101 00(永远补0代表正中间的格子)0 100)，值为0，
		对应的8个格的数值和表示的有效点周围的分布情况分别为：
		[1][0][1]      [ ][X][ ]
		[0][X][0]  ==> [X][X][X]
		[1][0][0]      [ ][X][X]
		有效点不删除。

		返回值：无。
		'''
		a3x3=[0 for _ in range(9)]
		a3x3[4]=0 # 中间的点总是有效点
		for i,x in enumerate(CGetImg.array):
			a3x3[0] = i & 1
			a3x3[1] = i & (1<<1)
			a3x3[2] = i & (1<<2)
			a3x3[3] = i & (1<<3)
			a3x3[5] = i & (1<<4)
			a3x3[6] = i & (1<<5)
			a3x3[7] = i & (1<<6)
			a3x3[8] = i & (1<<7)

			tmp=list(bin(i)[2:].zfill(8))
			tmp.reverse()
			tmp.insert(4,'0')
##			tmpout=map(lambda x: '[X]' if x=='0' else '[ ]',tmp)

			toout=map(lambda x: '[X]' if x==0 else '[ ]',a3x3)
##			assert toout==tmpout
			debug(' %d (%s) is %s:\n\t%s',i,''.join(tmp), 'REMOVE' if x else 'KEEP','\n\t'.join([ ''.join((toout[idx:idx+3])) for idx in range(0,9,3)]))
##			if (not (i % 20)) and i:
##				raw_input('press ENTER to continue ...')


	def getSampleInOneFile(self,w,h,nr_w,nr_h,filename):
		u'''获取 一次获取多张相同大小的图片并放到同一个文件中。文件中每行 @nr_w 个共 @nr_h 行，图片的宽度高度分别为 @w,@h，
		保存到 @ filename 中(全路径)。
		'''
		info,debug=self.logger.info,self.logger.debug
		im=Image.new('RGB',(w*nr_w,h*nr_h))
		for i in range(nr_w*nr_h):
			r,_,_=self._getResponse('http://www.sbanzu.com/sbanzu/GetCode.asp'.encode(self.dft_img_encoding))
			if not r:
				info('cant\'t get code image!')
				return False
			else:
				debug('got %02d',i)
				t=GifImagePlugin.GifImageFile(StringIO(r))
				t.seek(0)
				try:
					t=t.convert('P')
				except IOError:
					t=t.convert('P')
				im.paste(t,(w*(i%nr_w),h*(i//nr_w)))
		im.save(filename)
		info('done.')


	def checkAccuracyRate(self,imgdir,sampledir):
		u'''计算对测试文件的识别准确率。@imgdir为测试文件夹，@sampledir为样本文件夹。
		返回值：无。
		'''
		info,debug=self.logger.info,self.logger.debug
		right_but_mis_cnt=0
		total,right,sec=0,0,0
		wrongs=[0 for _ in range(10)]
		seconds=[0 for _ in range(10)]
		info('start check ...')
##		for dirpath,dirnames,filenames in os.walk(imgdir,False):
		for i in range(1,10):

			if i in (1,3,7,8,2,4,6,5):
				debug('skip %d.',i)
				continue

			debug('checking %d ...',i)
			for fname in glob.iglob(os.path.join(os.path.join(imgdir,str(i)),'*.png')):
				total+=1
				if total and total%50==0:
					info('processed %d',total)

##				if total not in (11, 16,23,28,42,53,64):
##					continue

##				if total not in (103,114,148,169,254,340,431,483,509,517):
##				if total not in (114,148,169,340,381,395,426,431,448,455,635,639,640,647,666,668):
##					continue

##				fname=os.path.join(dirpath,f)
##				f=os.path.basename(fname)
##				debug('processing %s ...',fname)
				im=PngImagePlugin.PngImageFile(fname)

				t=self.binarizationImg(im)
##				t=self.preSmartSplitImg(im)
				tmp=t
				t=self.removeInsularPoint(t)
##				t=self.Xihua(t)
				t=self.keepEdge(t)
				t=self.removeSpace(t)
				sc=self.calSC(t)
				cost= self.calCost(sampledir,sc,0.1)
##				modcost=self.postProcess(cost,t)

				#
				allpixcnt, pixcnt=self.getPixCnt(tmp)
##				assert pixcnt>0

				# 此段代码使整个时间 double 了，不过整体识别率提高了 2%，对“9”的识别率提高了 5%
				threshold=0.1
				while float(cost[0][1])/pixcnt<0.8 or cost[0][1]-cost[1][1]==0:
					threshold+=0.03
					if threshold>0.3: break
					cost=self.calCost(sampledir,sc, threshold)
##					debug('No.%d with %.2f as threshold: %.1f%% %s', total, threshold, float(cost[0][1])/pixcnt*100, cost)
				if float(cost[0][1])/pixcnt<0.8 :
					pass
##					debug('No.%d failed! with %.2f as threshold: %s', total, threshold,cost)


				if cost[0][0]==i:
##					if cost[0][2]<cost[1][2]:
##						right_but_mis_cnt+=1
##						debug('%d right but mis cost=%s',i,cost)
					right+=1
##					debug('\nNo.%d %d right, cost=%s\n', total, i, cost)
##					debug('\nNo.%d %d right, %.2f%%, cost=%s\n', total, i, float(cost[0][1])/pixcnt, cost)
				else:
					wrongs[i]+=1
##					debug('\n\n%s No.%d should %d, cost=%s',self.img2string(tmp),total,i,cost)
					debug('\n\n%s No.%d should %d, %.1f%%, cost=%s',self.img2string(tmp),total,i, float(cost[0][1])/pixcnt*100, cost)
##					debug('No.%d should %d, cost=%s',total,i,cost)
					if cost[1][0]==i:
##						debug('%d should be %d (second candidate)',cost[0][0],int(f[0]))
						seconds[i]+=1
						sec+=1
##					else:
##							debug('%d should be %d',cost[0][0],int(f[0]))


##				if len([x for x in cost if x[1]!=0])<5:
##					threshold=0.1
##					while threshold <= 0.5:
##						threshold+=0.05
##						cost2=self.calCost(sampledir,sc, threshold)
##						if len([x for x in cost2 if x[1]!=0])>=5:
##							debug('No.%d with %.2f as threshold: %.2f %s', total, threshold, float(cost2[0][1])/pixcnt, cost2)
##							break
##					else:
##						debug('failed! with %.2f as threshold: %s',threshold,cost2)

		info('total: %d, right %d, percent=%.2f, %d\n\tseconds:%s\n\twrongs:%s',total,right,right/float(total),
				 sec,seconds[1:],wrongs[1:])
		info('right_but_mis_cnt=%d',right_but_mis_cnt)


	def getMultiImg(self,savepath,num=10):
		u''' 获取 @num 张图片，并保存到目录 @savepath 中。
		需要手工输入 文件名，也就是图片上的四位数字。
		返回值：无。
		'''
		info,debug=self.logger.info,self.logger.debug
		for i in range(num):
			r,_,_=self._getResponse('http://www.sbanzu.com/sbanzu/GetCode.asp'.encode(self.dft_img_encoding))
			if not r:
				info('cant\'t get code image!')
				return False
			else:
				im=GifImagePlugin.GifImageFile(StringIO(r))
				im.seek(0)

				t=self.binarizationImg(im)
				info('after binarizationImg():\n%s',self.img2string(t))
				f=raw_input('%d/%d) input file name:'%(i+1,num))
				if not f:
					info('skip~~~~~')
					continue
				fpath=os.path.join(savepath,f+'.gif')
				if os.access(fpath,os.F_OK):
					info('file with same name exists!')
					fpath=os.path.join(savepath,f+'%d.gif'%time.time()) # second is enough
				open(fpath,'wb').write(r)
				info('%s saved.',fpath)


	def saveSample(self,sampledir):
		u''' 从网络获取图片文件，获得SC信息，保存信息文件(.dat)和相应的字符图片到目录 @ sampledir 中。
		需要手工输入文件名，也就是图片上的数字。如果同名文件存在则会被覆盖。
		返回值：无。
		'''
		info,debug=self.logger.info,self.logger.debug
		while True:
			r,_,_=self._getResponse('http://www.sbanzu.com/sbanzu/GetCode.asp'.encode(self.dft_img_encoding))
			if not r:
				info('cant\'t get code image!')
				return False
			else:
				rawim=GifImagePlugin.GifImageFile(StringIO(r))
				rawim.seek(0)

			t=self.binarizationImg(rawim)
##			info('after binarizationImg():\n%s',self.img2string(t))

			t=self.removeInsularPoint(t)
##			info('after removeInsularPoint():\n%s',self.img2string(t))

##			info('after keepEdge():\n%s',self.img2string(t))
			t=self.keepEdge(t)

			info('after splitImg():\n')
			t=self.splitImg(t)

			# 根据切割的宽度反向获取未二值化的单个字符的图片
			rawchars=[]
			x=0
			for item in t:
				rawchars.append(rawim.crop((x,0,x+item.size[0],rawim.size[1])))
				x+=item.size[0]

			info('after removeSpace():\n')
			t=[ self.removeSpace(x) for x in t]

			info('calSC\n')
			sclist=[]
			for idx, im in enumerate(t):
				st=self.calSC(im)
##				sclist.append((im,st))
				info('\t %d)\n%s',idx,self.img2string(im))
				f=raw_input('input file base name to save(ENTER to skip, q to exit): ')
				if f=='':
					info('skip ~~~')
					continue
				elif f in 'qQ':
					info('exit ~~~')
					return
				fpath=os.path.join(sampledir,f+'.dat')
				cPickle.dump(st,open(fpath,'wb'))
				info('%s saved.',fpath)
				fpath=os.path.join(sampledir,f+'.gif')
				rawchars[idx].save(fpath,'gif')
				info('%s saved.',fpath)


	def updateSampleData(self,sampledir):
		u'''重新计算 @sampledir 下的样本文件(每个字符的 .gif 文件)的 sc 数据，写入对应的 .dat 文件中。原 .dat 被覆盖。
		返回值：无。
		'''
		info,debug=self.logger.info,self.logger.debug
		for i in range(1,10):
			fname=os.path.join(sampledir,'%d.gif'%i)
			rawim=GifImagePlugin.GifImageFile(fname)
			rawim.seek(0)
			t=self.binarizationImg(rawim)
##			info('after binarizationImg():\n%s',self.img2string(t))
			t=self.removeInsularPoint(t)
##			info('after removeInsularPoint():\n%s',self.img2string(t))
			t=self.keepEdge(t)
##			info('after keepEdge():\n%s',self.img2string(t))
			st=self.calSC(t)

			fpath=os.path.join(sampledir,'%d.dat'%i)
			cPickle.dump(st,open(fpath,'wb'))
			debug('%s ==> %s',fname,fpath)


	@staticmethod
	def _HThin(im,w,h,magic=0,bk=255):
		u''' 横向扫描图片 @im 执行细化。
		@im 要处理的图片。
		@w 图片的宽度。
		@h 图片的高度。
		@magic 有效像素的值。
		@bk 背景像素的值。
		返回值：处理后的图片文件。
		'''
		array=CGetImg.array
		NEXT = 1
		for i in range(h):
			for j in range(w):
				if NEXT == 0:
					NEXT = 1
				else:
					M = im[j-1,i]+im[j,i]+im[j+1,i] if 0<j<w-1 else 1
					if im[j,i] == magic  and M != 0:
						a = [0]*9
						for k in range(3):
							for l in range(3):
								if -1<(i-1+k)<h and -1<(j-1+l)<w and im[j-1+l,i-1+k]==bk:
									a[k*3+l] = 1
						sum = a[0]*1+a[1]*2+a[2]*4+a[3]*8+a[5]*16+a[6]*32+a[7]*64+a[8]*128
						im[j,i] = array[sum]*bk
						if array[sum] == 1:
##							debug('remove %d,%d',j,i)
							NEXT = 0


	@staticmethod
	def _VThin(im,w,h,magic=0,bk=255):
		u''' 纵向扫描图片 @im 执行细化。
		@im 要处理的图片。
		@w 图片的宽度。
		@h 图片的高度。
		@magic 有效像素的值。
		@bk 背景像素的值。
		返回值：处理后的图片文件。
		'''
		array=CGetImg.array
		NEXT = 1
		for j in range(w):
			for i in range(h):
				if NEXT == 0:
					NEXT = 1
				else:
					M = im[j,i-1]+im[j,i]+im[j,i+1] if 0<i<h-1 else 1
					if im[j,i] == magic and M != 0:
						a = [0]*9
						for k in range(3):
							for l in range(3):
								if -1<(i-1+k)<h and -1<(j-1+l)<w and im[j-1+l,i-1+k]==bk:
									a[k*3+l] = 1
						sum = a[0]*1+a[1]*2+a[2]*4+a[3]*8+a[5]*16+a[6]*32+a[7]*64+a[8]*128
						im[j,i] = array[sum]*bk
						if array[sum] == 1:
##							debug('remove %d,%d',j,i)
							NEXT = 0


	@staticmethod
	def Xihua(im,magic=0,num=5):
		u''' 细化图片 @im， @num 指定横向纵向的处理次数，@magic 指定有效像素的值。
		参考：python 简单图像处理（16） 图像的细化（骨架抽取） http://www.cnblogs.com/xianglan/archive/2011/01/01/1923779.html 。
		返回值：处理后的图片文件。
		'''
		w,h=im.size
		pix=im.load()
		for i in range(num):
			CGetImg._VThin(pix,w,h,magic)
##			debug('%d V \n%s',i,CGetImg.img2string(im))
			CGetImg._HThin(pix,w,h,magic)
##			debug('%d H \n%s',i,CGetImg.img2string(im))

##		debug('after Xihua():\n%s',CGetImg.img2string(im))
		return im


	@staticmethod
	def postProcess(cost,im):
		if cost[0][0]==5:
			pass
		elif cost[0][0]==8:
			pass

		return cost



	@staticmethod
	def splitImg(im,magic=0):
		u'''分割图片 @im 中的字符，返回包含字符图片的列表。 @magic 指定有效像素的值。
		返回值：分割后的图片对象列表。
		'''
		result=[]
		# 80个像素有四个数字，则每个大概是20个像素宽(包含空白)。因此每20个像素为一个大致的纵向分割点，并在此左右5个
		# 像素的宽度内查找最佳纵向分割点。最佳纵向分割点是需要切开的连通线条最细(纵方向像素数最少)的位置的x坐标。
		parts=[0,]# 记录3个最佳切割点
		for x in range(20,im.size[0],20):
			mincnt,bestidx=im.size[1],None
			for i in range(x-5,x+5,1):
				col=im.crop((i,0,i+1,im.size[1]))
				cnt=list(col.getdata()).count(magic)
				if mincnt>=cnt:
					mincnt=cnt
					bestidx=i
				if cnt==0: # 字符不沾连，直接以i为最佳分割点
##					debug('cnt==0 at %d',i)
##					bestidx=i
					break
##				else:
##					debug('cnt==%d at %d',cnt,i)
			parts.append(bestidx)
		parts.append(79)

##		debug('parts=%s',parts)
		for idx in range(len(parts)-1):
			result.append(im.crop((parts[idx],0,parts[idx+1],im.size[1])))

##		debug('after splitImg():\n')
##		for idx,item in enumerate(result):
##			debug('\t %d)\n%s',idx,CGetImg.img2string(item))
		return result


	@staticmethod
	def binarizationImg(im):
		u'''去噪并二值化图像。
		返回值：处理后的图片对象。
		'''
##		info,debug=self.logger.info,self.logger.debug
		try:
			im=im.convert('L')
		except IOError:
			im=im.convert('L')
		im=im.filter(ImageFilter.MedianFilter())
		im=ImageEnhance.Contrast(im).enhance(1.5).convert('1')
		return im





if __name__=='__main__':
##	points=((0.2,0.5),(0.4,0.5),(0.3,0.4),(0.15,0.3),(0.3,0.2),(0.45,0.3))
##	points=((0.2,-0.5),(0.4,-0.5),(0.3,-0.4),(0.15,-0.3),(0.3,-0.2),(0.45,-0.3))
##	test_setRadiusMatrix(points)
##	sys.exit()

	c=CGetImg_test(os.path.expanduser('~/tmp.cookie'))
##	c.getSampleInOneFile(80,20,5,8,os.path.expanduser('~/100.png'))
##	c.doWork()
	tmppath='/home/kevin/proj/hg/postgetter-app/scimgdata/'
##	c.getMultiImg(tmppath,30)
##	sys.exit()

##	c.testSplitImg('/home/kevin/proj/hg/postgetter-app/scimgdata/',
##	               '/home/kevin/proj/hg/postgetter-app/scimgdata/',
##	               '/home/kevin/proj/hg/postgetter-app/scimgdata/spliterr/')
##	sys.exit()

	samplepaths={
		'normal':'/home/kevin/proj/hg/postgetter-app/scimgdata/normal/',
		'normal-bk':'/home/kevin/proj/hg/postgetter-app/scimgdata/normal-bk/',
		'normal-test':'/home/kevin/proj/hg/postgetter-app/scimgdata/normal-test/',
		'normal-tmp':'/home/kevin/proj/hg/postgetter-app/scimgdata/normal-tmp/',
		'normal-shit':'/home/kevin/proj/hg/postgetter-app/scimgdata/normal-shit/'
	}
##	c.saveSample(samplepaths['normal-test'])

##	c.checkAccuracyRate('/home/kevin/proj/hg/postgetter-app/scimgdata/',samplepaths['normal']) # 356, right 309, acc rate=0.87, 24
##	c.checkAccuracyRate('/home/kevin/proj/hg/postgetter-app/scimgdata/',samplepaths['normal-bk']) # 356, right 314, acc rate=0.88, [0, 0, 5, 0, 2, 5, 7, 0, 3, 20]
##	c.checkAccuracyRate('/home/kevin/proj/hg/postgetter-app/scimgdata/',samplepaths['normal-test']) # 356, right 314, acc rate=0.88, [0, 0, 5, 0, 2, 5, 7, 0, 3, 20]
##	c.checkAccuracyRate('/home/kevin/proj/hg/postgetter-app/scimgdata/',samplepaths['normal-tmp']) # 356, right 314, acc rate=0.88, [0, 0, 5, 0, 2, 5, 7, 0, 3, 20]
##	cProfile.run('''c.checkAccuracyRate('/home/kevin/proj/hg/postgetter-app/scimgdata/',samplepaths['normal-shit'])''','/home/kevin/captcha-profile.dat')
##	p=pstats.Stats('/home/kevin/captcha-profile.dat')
##	p.sort_stats('time','cumulative').print_stats('')
	c.checkAccuracyRate('/home/kevin/proj/hg/postgetter-app/scimgdata/',samplepaths['normal-shit']) # 356, right 314, acc rate=0.88, [0, 0, 5, 0, 2, 5, 7, 0, 3, 20]

##	c.trySplitImg('/home/kevin/proj/hg/postgetter-app/scimgdata/spliterr/')
##	c.updateSampleData(samplepaths['normal-tmp'])


	sys.exit()
